package nuder;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;

import titt.TITT;
import titt.TITTNuder;
import titt.head.response.FileHead;
import titt.io.IOFactory;
import titt.service.content.NuderRequest;
import titt.service.content.NuderResponse;
import titt.util.io.pipe.PipeListener;
import titt.util.io.pipe.StreamPiper;

/**
 * An interface to communicate with a kkloud service.
 * Can puff, get, create a kkloud, and retreive all files from a given kkloud.
 */
public class KKloudDesktop {
	/* PROXY INTERFACES */
	private interface KKloudFile {
		NuderResponse puff(String id, int updateInterval, NuderRequest req); 
		NuderResponse foo(NuderRequest req);
	}
	private interface KKloudFileStream {
		NuderResponse get();
	}
	private interface KKloudInstance {
		String[] getListOfFiles();
		boolean uploadFile(String filename);
		void puff(String id);
	}
	private interface KKloudInstanceStream  {
		NuderResponse get(String path);
		String puff(String filename, String uniqueID, int updateInterval, NuderRequest nreq);
	}

	/**
	 * Local KKloudInstance that will be registered as a titt service
	 * Unless you know what you are doing, ignore this class.
	 * It just has to be public/protected because titt needs this
	 */
	protected class LocalKKloudInstance {
		private KKloudInstanceStream remKIS;
		private int uniqueID = 0;
		final private String PATH;
		
		public LocalKKloudInstance(String id, String path) {
			remKIS = TITTNuder.createProxy(id);
			if (!path.endsWith("/"))
				PATH = path + "/";
			else
				PATH = path;
		}
		
		public String[] getListOfFiles() {
			ArrayList<String> res =  new ArrayList<String>();
			for (File f : new File(PATH).listFiles())
				if (f.isFile())
					res.add(f.getName());
			return res.toArray(new String[]{});
		}

		public boolean uploadFile(final String filename) {
			// have to be in thread?
			new Thread() {
				public void run() {
					String fileID = "s" + (uniqueID++);
					remKIS.puff(filename, fileID, 3500, new NuderRequest(new File(PATH + filename)));
				}
			}.start();
				
			return true;
		}
		public void onInitiate(int total, String[] ids) { System.out.println("LocalKKloudInstance.onInitiate: total=" + total); }
		public void onError(String[] ids) { System.out.println("LocalKKloudInstance.onError");}
		public void onComplete(int total, String[] ids) { System.out.println("LocalKKloudInstance.onComplete: total=" + total);	}
		public void onConnectionUpdate(int read, int total, String[] ids) { System.out.println("LocalKKloudInstance.onConnectionUpdate: read=" + read + ", total=" + total); }
	}
	
	/**
	 * Monitor that will be registered as a titt service on the kkloud server.
	 * Unless you know what you are doing, ignore this class.
	 * It just has to be public/protected because titt needs this
	 */
	protected class Monitor implements PipeListener {
		public void onComplete(int total, String[] ids) { System.out.println("Monitor.onComplete, total=" + total);}
		public void onConnectionUpdate(int read, int total, String[] ids) { System.out.println("Monitor.onConnectionUpdate, read=" + read + ", total=" + total);}
		public void onError(String[] ids) { System.out.println("Monitor.onError");}
		public void onInitiate(int total, String[] ids) { System.out.println("Monitor.onInitiate, total=" + total);}
		public void onComplete(int total) { System.out.println("Monitor.onComplete, total=" + total);}
		public void onConnectionUpdate(int read, int total) { System.out.println("Monitor.onConnectionUpdate, read=" + read + ", total= " + total);}
		public void onError() { System.out.println("Monitor.onError");}
		public void onInitiate(int total) { System.out.println("Monitor.onInitiate, total=" + total);}
	}
	public Monitor monitor = new Monitor();

	/**
	 * Creates a new kkloud on a given id, with a given local path to a folder that is going to be shared
	 */
	public void createKKloud(String id, String path) {
		LocalKKloudInstance locKI = new LocalKKloudInstance(id, path);
		TITT.register(locKI, "kkloud", "manager", "monitor", id, "*");

		KKloudInstance remKI = TITT.createProxy("kkloud", "instance");
		remKI.puff(id);
	}

	/**
	 * Creates an instance of KKloudDesktop which connects to the main Internet kkloud server
	 */
	public KKloudDesktop() { this(80, "kkloud.dk"); }
	
	/**
	 * Creates an instance of KKloudDesktop which connect to a host:port of own choice
	 */
	public KKloudDesktop(int port, String host) { TITT.config(port, host); }
	
	/**
	 * Puffs a file on a random id (kkloud does not currently give any security as to whether this
	 * id is taken or not)
	 */
	public void puff(File f) throws Exception {
		puff(f, new BigInteger(32, new SecureRandom()).toString(32)); 
	}

	/**
	 * Puffs a file on a given id
	 */
	public void puff(File f, String id) throws Exception {
		TITT.register(monitor, "kkloud", "manager", "monitor", id);
		KKloudFile proxy = TITTNuder.createProxy("kkloud", "file");
		System.out.println("Puffing " + f.getName() + " on " + id);
		proxy.puff(id, 1500, new NuderRequest(f));
		TITT.unregister(monitor);
	}

	/**
	 * Fetches a given file from an id
	 * @param id the id of the shared file (not the filename)
	 */
	public void fetch(String id) throws Exception {
		KKloudFileStream kfs = TITTNuder.createProxy(id);
		fetch("./", kfs.get());
	}

	/**
	 * Fetches a given file from a kkloud
	 * @param folder where to save the file to
	 * @param id the array of where the file is, if the kkloud's id is foo and the name of the file is
	 *        foobar.baz the id array will be ["foo", "foobar.baz"] 
	 */
	public void fetch(String folder, String... id) throws Exception {
		String path = "";
		for (int i=0; i<id.length; i++)
			path +=  "/" + id[i];
		
		KKloudInstanceStream proxy = TITTNuder.createProxy(id[0]);
		fetch(folder, proxy.get(path));
	}
	
	private void fetch(String folder, NuderResponse nr) throws Exception {
		FileHead fh = new FileHead(nr.getHead());
		String filename = fh.getFilename();
		int length = nr.getLength();

		new File(folder).mkdir();
		OutputStream out = new BufferedOutputStream(new FileOutputStream(folder + "/" + filename));

		StreamPiper sp = IOFactory.createStreamPiper();
		sp.pipe(nr.getStream().asStream(), out, length, monitor);
	}

	/**
	 * Download all files from a given kkloud
	 * @param id the id of the kkloud
	 */
	public void getAllFiles(String id) { 
		KKloudInstance proxy = TITT.createProxy("kkloud", "manager", "monitor", id);
		String folder = id;
		
		for (String s : proxy.getListOfFiles()) {
			try {
				fetch(folder, id, s);
			}
			catch(Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Provides a textual interface for KKloudDesktop
	 */
	public static void main(String... args) throws Exception {
		KKloudDesktop kd = new KKloudDesktop();

		if (args.length < 2) exit("Not enough arguments\n\n" + help);
		
		String command = args[0].toLowerCase();
	
		if (command.equals("--fetch") || command.equals("-f")) {
			kd.fetch(args[1]/*id*/);
		} else if (command.equals("--puff") || command.equals("-p")) {
			String fname = args[1];
			if (!new File(fname).exists()) exit("File does not exist");
			if (args.length > 2)
				kd.puff(new File(fname), args[2]);
			else
				kd.puff(new File(fname));
		} else if (command.equals("--create-kkloud") || command.equals("-ck")) {
			if (args.length < 3) exit("Not enough arguments\n\n" + help);
			kd.createKKloud(args[1]/*id*/, args[2]/*path*/);
		} else if (command.equals("--download-all") || command.equals("-da")) {
			kd.getAllFiles(args[1]/*id*/);
		} else {
			exit(help);
		}
	}
	
	private static String help =
		"Run KKloudDesktop with\n" +
		" -puff <filename> [id]       ; puffs the given file. if no id is given, a random\n" +
		" -p <filename> [id]            one is generated\n" +
		" --fetch <id>                ; fetch the file puffed at the given id\n" +
		" -f <id>\n" +
		" --create-kkloud <id> <path> ; create a kkloud on the given id, and share the\n" +
		" -ck <id> <path>               the content of path\n" +
		" --download-all <id>         ; download all files from the kkloud at the id\n" + 
		" -da <id>";
	
	public static void exit(String errorMessage) {
		System.out.println(errorMessage);
		System.exit(1);
	}
}
